using System;
using System.Collections.Generic;
using System.Text;

using Mono.Cecil;
using Mono.Cecil.Cil;

using CILUtil;

namespace CILFlow
{
    public class PopTreeList
    {
        public CILInstructionBlock Block;
        public List<PopTree> PopTrees = new List<PopTree>();
        public void Add( PopTree tree )
        {
            PopTrees.Add( tree );
        }
    }

    public class TypedToken
    {
        public TypedToken(Instruction instruction)
        {
            this.Instruction = instruction;
        }
        public Instruction Instruction;
        public object Entity;
        public string EntityType;
    }

    // CODE:
    //  m1( a1() ).m2( a2() ).m3( a3() )

    // CIL:
    // ld this  ; push <this>
    // dup      ; push <this>
    // call a1  ; pop <this> -> push <value>
    // call m1  ; pop <this> pop<value> -> push<obj>
    // ld this  ; push <this>
    // call a2  ; pop <this> -> push <value2>
    // call m2  ; pop <obj> pop<value2> -> push<obj2>
    // ld this  ; push <this>
    // call a3  ; pop <this> -> push <value3>
    // call m3  ; pop <obj2> pop<value3> -> push<obj3>

    // POP TREE:
    //                [m3]
    //            [m2]    [a3]
    //        [m1]   [a2]    [this]   
    //   [this]  [a1]   [this]   
    //              [this]
    public class PopTree
    {
        public MethodDefinition _md;
        public Instruction _instruction;
        public List<PopTree> _PopList;
        public PopTree( Instruction instruction, MethodDefinition md )
        {
            _instruction = instruction;
            _PopList = new List<PopTree>();
            _md = md;
        }

        public void Consume(PopTree t)
        {
            _PopList.Add(t);
        }

        public System.Collections.ArrayList Flatten()
        {
            System.Collections.ArrayList list = new System.Collections.ArrayList();
            Flatten(this, list);
            return list;
        }

        public delegate void VisitPopTree(PopTree tree, PopTree parent );
        public void Visit(VisitPopTree action)
        {
            Visit(this, null, action);
        }
        protected void Visit(PopTree tree, PopTree parent, VisitPopTree action)
        {
            action(tree, parent);
            if (tree._PopList.Count > 0)
            {
                foreach (PopTree child in tree._PopList)
                {
                    Visit(child, tree, action);
                }
            }
        }

        public void VisitPost(VisitPopTree action)
        {
            VisitPost(this, null, action);
        }
        protected void VisitPost(PopTree tree, PopTree parent, VisitPopTree action)
        {
            if (tree._PopList.Count > 0)
            {
                foreach (PopTree child in tree._PopList)
                {
                    Visit(child, tree, action);
                }
            }
            action(tree, parent);
        }

        private string Quote(PopTree tree, MethodDefinition md)
        {
            Instruction i = tree._instruction;
            object operand = i.Operand == null ? CILUtil.Entity.GetOperand(md, i) : i.Operand;
            if (operand == null )
                operand = "";
            string label = tree._instruction.OpCode.ToString() + ":" + operand;
            return tree._instruction.GetHashCode() + " [label=\"" + label + "\"]";
        }
        public string ToDot(MethodDefinition md)
        {
            StringBuilder buffer = new StringBuilder();
            buffer.AppendLine("digraph G {");
            ToDot(md, buffer);
            buffer.AppendLine("}");
            return buffer.ToString();
        }

        public void ToDot(MethodDefinition md, StringBuilder buffer)
        {
            this.Visit(delegate(PopTree vTree, PopTree vParent)
            {
                if (vParent == null && vTree._PopList.Count == 0)
                {
                    buffer.AppendLine(Quote(vTree, md) + ";");
                }
                else if (vParent == null)
                    return;
                else
                {
                    //buffer.AppendLine(Quote(vParent, md) + "--" + Quote(vTree, md) + ";");
                    buffer.AppendLine(Quote(vParent, md) +";");
                    buffer.AppendLine(Quote(vTree, md) + ";");
                    buffer.AppendLine(vParent._instruction.GetHashCode() + " -> " + vTree._instruction.GetHashCode() + ";");
                }
            });
        }

        public void Flatten(PopTree tree, System.Collections.ArrayList list)
        {
            list.Add(new TypedToken(tree._instruction));
            if (tree._PopList.Count > 0)
            {
                //if (tree._instruction.OpCode.Name.StartsWith("st"))
                //    list.Add("=");
                list.Add("#");
                foreach (PopTree child in tree._PopList)
                {
                    Flatten(child, list);
                }
                list.Add("#");
            }
        }

        public int Depth
        {
            get
            {
                if (_PopList.Count == 0)
                    return 0;
                int max = 0;
                foreach (PopTree t in _PopList)
                {
                    int d = t.Depth;
                    if (d > max)
                        max = d;
                }
                return max + 1;
            }
        }

        public System.Collections.ArrayList FilteredTops(params OpCode[] opcodes)
        {
            System.Collections.ArrayList tops = new System.Collections.ArrayList();
            PopTree current = this;
            while (current != null)
            {
                // Want Continous Chain.
                if (!IsOpCode(opcodes, current._instruction.OpCode))
                    return tops;
                tops.Add(current);
                current = current.LastChild;
            }
            return tops;
        }

        private bool IsOpCode(OpCode[] opcodes, OpCode testCode)
        {
            foreach (OpCode opcode in opcodes)
            {
                if (testCode == opcode)
                    return true;
            }
            return false;
        }

        public System.Collections.ArrayList Tops
        {
            get
            {
                System.Collections.ArrayList tops = new System.Collections.ArrayList();
                PopTree current = this;
                while (current != null)
                {
                    tops.Add(current);
                    current = current.LastChild;
                }
                return tops;
            }
        }

        public PopTree LastChild
        {
            get
            {
                if (_PopList.Count > 0)
                    return (PopTree)_PopList[_PopList.Count-1];
                return null;
            }
        }

        public System.Collections.ArrayList Predicate(MethodDefinition md)
        {
            if (CodeProperties.IsBlockEnd(_instruction))
            {
                return CollectEntities(md);
            }
            return null;
        }

        public System.Collections.ArrayList CollectEntities(MethodDefinition md)
        {
            System.Collections.ArrayList entities = new System.Collections.ArrayList();
            CollectEntities(md, this, entities);
            return entities;
        }
        private void CollectEntities(MethodDefinition md, PopTree tree, System.Collections.ArrayList entities)
        {
            object entity = Entity.GetEntity(md, tree._instruction);
            if (entity != null)
            {
                entities.Add(entity);
            }
            foreach (PopTree child in tree._PopList)
            {
                CollectEntities(md, child, entities);
            }
        }
    }

    public class PopTreeListBuilder
    {
        public Stack<PopTree> _machineStack = new Stack<PopTree>();
        PopTreeList _popTreeList = new PopTreeList();

        public static PopTreeList Construct(MethodDefinition md, CILInstructionBlock block)
        {
            PopTreeListBuilder builder = new PopTreeListBuilder();
            return builder.Build(md, block);
        }

        public PopTreeList Build(MethodDefinition md, CILInstructionBlock block)
        {
            //if (block.IsCatchBlock)
            //{
            //    CilWorker worker = md.Body.CilWorker;
            //    Instruction pushException = worker.Create(OpCodes.Nop);
            //    _machineStack.Push(new PopTree(pushException));
            //}
            Build(block.Instructions,md);
            _popTreeList.Block = block;
            return _popTreeList;
        }

        public PopTreeList Build(System.Collections.ICollection instructions, MethodDefinition md)
        {
            foreach (Instruction i in instructions)
            {
                PopTree p = new PopTree(i,md);
                int pops = InstructionInfo.Pops(i,_machineStack.Count);
                if (pops > _machineStack.Count)
                {
                    // can happen with exception blocks whose support is flaky.
                    //Console.WriteLine("Attempting to pop more than machine has: " + i.OpCode.ToString() + " " + i.Operand);
                    //return null;

                    // Happens with conditional assignments i == 0 ? -1 : 1
                    _popTreeList.Add(p);
                    return _popTreeList;
                }
                for (int x = 0; x < pops; x++)
                {
                    p.Consume(Pop());
                }
                if (InstructionInfo.Pushes(i))
                {
                    Push(p);
                }
                else
                {
                    _popTreeList.Add(p);
                }
            }
            if (_machineStack.Count > 0)
            {
                _popTreeList.PopTrees.AddRange(_machineStack);
            }
            return _popTreeList;
        }

        public void Push( PopTree t)
        {
            _machineStack.Push( t );
        }
        public PopTree Pop()
        {
            return (PopTree)_machineStack.Pop();
        }
    }
}
